package com.chenjia.proto.converter.benchmark;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.lang.invoke.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.BiConsumer;
import java.util.function.Function;

/**
 * 测试方法调用的性能：直接调用，反射调用，MethodHandle 调用，LambdaMetafactory
 * <p>
 * 测试项目：
 * <ol>
 *     <li>构造对象，并依次调用 set 方法设置值</li>
 *     <li>调用对象的 get 方法，并将值返回</li>
 * </ol>
 */
@State(Scope.Benchmark)
public class MethodInvokeBenchmark {

    public static void main(String[] args) throws RunnerException {
        Options options = new OptionsBuilder()
                .include(MethodInvokeBenchmark.class.getSimpleName())
                .forks(1)
                .mode(Mode.Throughput)
                .warmupIterations(2)
                .measurementIterations(2)
                .build();

        new Runner(options).run();
    }

    private Method setId;
    private Method setName;
    private Method setShopPrice;
    private Method setShippingFee;

    private static MethodHandle setIdHandle;
    private static MethodHandle setNameHandle;
    private static MethodHandle setShopPriceHandle;
    private static MethodHandle setShippingFeeHandle;

    private BiConsumer setIdBiConsumer;
    private BiConsumer setNameBiConsumer;
    private BiConsumer setShopPriceBiConsumer;
    private BiConsumer setShippingFeeBiConsumer;

    private Method getId;
    private Method getName;
    private Method getShopPrice;
    private Method isShippingFee;

    private static MethodHandle getIdHandle;
    private static MethodHandle getNameHandle;
    private static MethodHandle getShopPriceHandle;
    private static MethodHandle isShippingFeeHandle;

    private Function getIdFunction;
    private Function getNameFunction;
    private Function getShopPriceFunction;
    private Function isShippingFeeFunction;

    private Merchant globalMerchant;

    static {
        try {
            setIdHandle = MethodHandles.lookup().findVirtual(Merchant.class, "setId", MethodType.methodType(void.class, int.class));
            setNameHandle = MethodHandles.lookup().findVirtual(Merchant.class, "setName", MethodType.methodType(void.class, String.class));
            setShopPriceHandle = MethodHandles.lookup().findVirtual(Merchant.class, "setShopPrice", MethodType.methodType(void.class, double.class));
            setShippingFeeHandle = MethodHandles.lookup().findVirtual(Merchant.class, "setShippingFee", MethodType.methodType(void.class, boolean.class));

            getIdHandle = MethodHandles.lookup().findVirtual(Merchant.class, "getId", MethodType.methodType(int.class));
            getNameHandle = MethodHandles.lookup().findVirtual(Merchant.class, "getName", MethodType.methodType(String.class));
            getShopPriceHandle = MethodHandles.lookup().findVirtual(Merchant.class, "getShopPrice", MethodType.methodType(double.class));
            isShippingFeeHandle = MethodHandles.lookup().findVirtual(Merchant.class, "isShippingFee", MethodType.methodType(boolean.class));
        } catch (NoSuchMethodException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    @Setup
    public void setup() throws Throwable {
        setId = Merchant.class.getDeclaredMethod("setId", int.class);
        setName = Merchant.class.getDeclaredMethod("setName", String.class);
        setShopPrice = Merchant.class.getDeclaredMethod("setShopPrice", double.class);
        setShippingFee = Merchant.class.getDeclaredMethod("setShippingFee", boolean.class);

        final MethodHandles.Lookup lookup = MethodHandles.lookup();
        final MethodType consumerMType = MethodType.methodType(void.class, Object.class, Object.class);
        final MethodType functionMethodType = MethodType.methodType(BiConsumer.class);
        // 入参（this，被set值），出参 void
        setIdBiConsumer = (BiConsumer) LambdaMetafactory.metafactory(lookup,
                "accept", functionMethodType, consumerMType,
                setIdHandle, MethodType.methodType(void.class, int.class)).getTarget().invokeExact();
        setNameBiConsumer = (BiConsumer) LambdaMetafactory.metafactory(lookup, "accept", functionMethodType, consumerMType,
                setNameHandle, MethodType.methodType(void.class, String.class)).getTarget().invokeExact();
        setShopPriceBiConsumer = (BiConsumer) LambdaMetafactory.metafactory(lookup, "accept", functionMethodType, consumerMType,
                setShopPriceHandle, MethodType.methodType(void.class, double.class)).getTarget().invokeExact();
        setShippingFeeBiConsumer = (BiConsumer) LambdaMetafactory.metafactory(lookup, "accept", functionMethodType, consumerMType,
                setShippingFeeHandle, MethodType.methodType(void.class, boolean.class)).getTarget().invokeExact();

        final Merchant merchant = new Merchant();
        merchant.setId(1);
        merchant.setName("chenjia");
        merchant.setShopPrice(1.1);
        merchant.setShippingFee(true);
        globalMerchant = merchant;

        getId = Merchant.class.getDeclaredMethod("getId");
        getName = Merchant.class.getDeclaredMethod("getName");
        getShopPrice = Merchant.class.getDeclaredMethod("getShopPrice");
        isShippingFee = Merchant.class.getDeclaredMethod("isShippingFee");

        final MethodType functionMType = MethodType.methodType(Object.class, Object.class);
        final MethodType methodType2 = MethodType.methodType(Function.class);
        getIdFunction = (Function) LambdaMetafactory.metafactory(lookup, "apply", methodType2, functionMType,
                getIdHandle, MethodType.methodType(int.class)).getTarget().invokeExact();
        getNameFunction = (Function) LambdaMetafactory.metafactory(lookup, "apply", methodType2, functionMType,
                getNameHandle, MethodType.methodType(String.class)).getTarget().invokeExact();
        getShopPriceFunction = (Function) LambdaMetafactory.metafactory(lookup, "apply", methodType2, functionMType,
                getShopPriceHandle, MethodType.methodType(double.class)).getTarget().invokeExact();
        isShippingFeeFunction = (Function) LambdaMetafactory.metafactory(lookup, "apply", methodType2, functionMType,
                isShippingFeeHandle, MethodType.methodType(boolean.class)).getTarget().invokeExact();
    }

    @Benchmark
    public Merchant nativeSet() {
        final Merchant merchant = new Merchant();
        merchant.setId(1);
        merchant.setName("chenjia");
        merchant.setShopPrice(1.1);
        merchant.setShippingFee(true);
        return merchant;
    }

    @Benchmark
    public Merchant reflectSet() throws InvocationTargetException, IllegalAccessException {
        final Merchant merchant = new Merchant();
        setId.invoke(merchant, 1);
        setName.invoke(merchant, "chenjia");
        setShopPrice.invoke(merchant, 1.1);
        setShippingFee.invoke(merchant, true);
        return merchant;
    }

    @Benchmark
    public Merchant invokeSet() throws Throwable {
        final Merchant merchant = new Merchant();
        setIdHandle.invoke(merchant, 1);
        setNameHandle.invoke(merchant, "chenjia");
        setShopPriceHandle.invoke(merchant, 1.1);
        setShippingFeeHandle.invoke(merchant, true);
        return merchant;
    }

    @Benchmark
    public Merchant invokeWithArgumentsSet() throws Throwable {
        final Merchant merchant = new Merchant();
        setIdHandle.invokeWithArguments(merchant, 1);
        setNameHandle.invokeWithArguments(merchant, "chenjia");
        setShopPriceHandle.invokeWithArguments(merchant, 1.1);
        setShippingFeeHandle.invokeWithArguments(merchant, true);
        return merchant;
    }

    @Benchmark
    public Merchant invokeExactSet() throws Throwable {
        final Merchant merchant = new Merchant();
        setIdHandle.invokeExact(merchant, 1);
        setNameHandle.invokeExact(merchant, "chenjia");
        setShopPriceHandle.invokeExact(merchant, 1.1);
        setShippingFeeHandle.invokeExact(merchant, true);
        return merchant;
    }

    @Benchmark
    public Merchant lambdaMetafactorySet() throws Throwable {
        final Merchant merchant = new Merchant();
//        setIdConsumer.accept();invokeExact(merchant, 1);
//        setNameConsumer.accept();invokeExact(merchant, "chenjia");
//        setShopPriceConsumer.accept();invokeExact(merchant, 1.1);
//        setShippingFeeConsumer.accept();invokeExact(merchant, true);
        return merchant;
    }


    // ----------------------------------------------------------

//    @Benchmark
//    public Merchant2 nativeGet() {
//        Merchant2 merchant2 = new Merchant2();
//        merchant2.setId(globalMerchant.getId());
//        merchant2.setName(globalMerchant.getName());
//        merchant2.setShopPrice(globalMerchant.getShopPrice());
//        merchant2.setShippingFee(globalMerchant.isShippingFee());
//        return merchant2;
//    }
//
//    @Benchmark
//    public Merchant2 reflectGet() throws InvocationTargetException, IllegalAccessException {
//        final Merchant2 merchant = new Merchant2();
//        merchant.setId((int) getId.invoke(globalMerchant));
//        merchant.setName((String) getName.invoke(globalMerchant));
//        merchant.setShopPrice((double) getShopPrice.invoke(globalMerchant));
//        merchant.setShippingFee((boolean) isShippingFee.invoke(globalMerchant));
//        return merchant;
//    }
//
//    @Benchmark
//    public Merchant2 invokeGet() throws Throwable {
//        final Merchant2 merchant = new Merchant2();
//        merchant.setId((int) getIdHandle.invoke(globalMerchant));
//        merchant.setName((String) getNameHandle.invoke(globalMerchant));
//        merchant.setShopPrice((double) getShopPriceHandle.invoke(globalMerchant));
//        merchant.setShippingFee((boolean) isShippingFeeHandle.invoke(globalMerchant));
//        return merchant;
//    }
//
//    @Benchmark
//    public Merchant2 invokeWithArgumentsGet() throws Throwable {
//        final Merchant2 merchant = new Merchant2();
//        merchant.setId((int) getIdHandle.invokeWithArguments(globalMerchant));
//        merchant.setName((String) getNameHandle.invokeWithArguments(globalMerchant));
//        merchant.setShopPrice((double) getShopPriceHandle.invokeWithArguments(globalMerchant));
//        merchant.setShippingFee((boolean) isShippingFeeHandle.invokeWithArguments(globalMerchant));
//        return merchant;
//    }
//
//    @Benchmark
//    public Merchant2 invokeExactGet() throws Throwable {
//        final Merchant2 merchant = new Merchant2();
//        merchant.setId((int) getIdHandle.invokeExact(globalMerchant));
//        merchant.setName((String) getNameHandle.invokeExact(globalMerchant));
//        merchant.setShopPrice((double) getShopPriceHandle.invokeExact(globalMerchant));
//        merchant.setShippingFee((boolean) isShippingFeeHandle.invokeExact(globalMerchant));
//        return merchant;
//    }

    public static class Merchant {
        private int id;
        private String name;
        private double shopPrice;
        private boolean shippingFee;

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public double getShopPrice() {
            return shopPrice;
        }

        public void setShopPrice(double shopPrice) {
            this.shopPrice = shopPrice;
        }

        public boolean isShippingFee() {
            return shippingFee;
        }

        public void setShippingFee(boolean shippingFee) {
            this.shippingFee = shippingFee;
        }
    }

    public static class Merchant2 {
        private int id;
        private String name;
        private double shopPrice;
        private boolean shippingFee;

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public double getShopPrice() {
            return shopPrice;
        }

        public void setShopPrice(double shopPrice) {
            this.shopPrice = shopPrice;
        }

        public boolean isShippingFee() {
            return shippingFee;
        }

        public void setShippingFee(boolean shippingFee) {
            this.shippingFee = shippingFee;
        }
    }
}
/*
// get
Benchmark                                      Mode  Cnt          Score   Error  Units
MethodInvokeBenchmark.invokeExactGet          thrpt    2   56840156.126          ops/s
MethodInvokeBenchmark.invokeGet               thrpt    2   54934999.553          ops/s
MethodInvokeBenchmark.invokeWithArgumentsGet  thrpt    2    1929885.263          ops/s
MethodInvokeBenchmark.nativeGet               thrpt    2  183618200.682          ops/s
MethodInvokeBenchmark.reflectGet              thrpt    2   35808458.054          ops/s

// get  将 MethodHandle 设为 static
Benchmark                                      Mode  Cnt          Score   Error  Units
MethodInvokeBenchmark.invokeExactGet          thrpt    2   59287967.087          ops/s
MethodInvokeBenchmark.invokeGet               thrpt    2   60391856.875          ops/s
MethodInvokeBenchmark.invokeWithArgumentsGet  thrpt    2    2295329.212          ops/s
MethodInvokeBenchmark.nativeGet               thrpt    2  180945775.163          ops/s
MethodInvokeBenchmark.reflectGet              thrpt    2   36899082.322          ops/s

// 将目标对象作为第一个参数传入
Benchmark                                      Mode  Cnt          Score   Error  Units
MethodInvokeBenchmark.invokeExactSet          thrpt    2   72240292.182          ops/s
MethodInvokeBenchmark.invokeSet               thrpt    2   72579469.451          ops/s
MethodInvokeBenchmark.invokeWithArgumentsSet  thrpt    2    2314716.650          ops/s
MethodInvokeBenchmark.nativeSet               thrpt    2  230606308.501          ops/s
MethodInvokeBenchmark.reflectSet              thrpt    2   31141756.566          ops/s

// 将 MethodHandle 设为 static：性能还真就更好了
Benchmark                                      Mode  Cnt          Score   Error  Units
MethodInvokeBenchmark.invokeExactSet          thrpt    2   79924447.526          ops/s
MethodInvokeBenchmark.invokeSet               thrpt    2   79838641.576          ops/s
MethodInvokeBenchmark.invokeWithArgumentsSet  thrpt    2    2251799.779          ops/s
MethodInvokeBenchmark.nativeSet               thrpt    2  229795323.975          ops/s
MethodInvokeBenchmark.reflectSet              thrpt    2   31977859.144          ops/s

// 调用前执行 bindTo
Benchmark                                      Mode  Cnt          Score   Error  Units
MethodInvokeBenchmark.invokeExactSet          thrpt    2    4294598.769          ops/s
MethodInvokeBenchmark.invokeSet               thrpt    2    3984967.166          ops/s
MethodInvokeBenchmark.invokeWithArgumentsSet  thrpt    2     845443.497          ops/s
MethodInvokeBenchmark.nativeSet               thrpt    2  228912205.308          ops/s
MethodInvokeBenchmark.reflectSet              thrpt    2   31103634.096          ops/s
 */